#include "io.h"
#include "sys-sdio.h"

struct fat32 sdft32;
struct bfile mfile;

volatile tina_mmc_t mmc=(tina_mmc_t)MMC0_BASE_ADDR;

rt_uint32_t sd_rca;

static inline void sdelay(int loops){
	__asm__ __volatile__ ("1:\n" "subs %0, %1, #1\n"
		"bne 1b":"=r" (loops):"0"(loops));
}

static inline rt_uint32_t GET_BITS(rt_uint32_t *resp,
                               rt_uint32_t  start,
                               rt_uint32_t  size){
        const rt_int32_t __size = size;
        const rt_uint32_t __mask = (__size < 32 ? 1 << __size : 0) - 1; 
        const rt_int32_t __off = 3 - ((start) / 32);
        const rt_int32_t __shft = (start) & 31;
        rt_uint32_t __res;

        __res = resp[__off] >> __shft;
        if (__size + __shft > 32)
            __res |= resp[__off-1] << ((32 - __shft) % 32);

        return __res & __mask;
}

void mmc_update_clk(void){
    rt_uint32_t cmd;
    rt_uint32_t timeout = 100;

    /* cmd load */
    cmd = SDXC_LOAD_CMD | SDXC_UPDATE_CLOCK_CMD | SDXC_WAIT_OVER_CMD;
    mmc->cmdr_reg = cmd;
    /* while load success */
    while ((mmc->cmdr_reg & SDXC_LOAD_CMD) && (--timeout)){
        sdelay(100000);
    }
    if (!timeout){
        logout("mmc update clk failed\r\n");
       
    }
    /* clean interrupt */
    mmc->risr_reg = mmc->risr_reg;
}

static void sdio_gpio_init(void){
    int pin;
    for (pin = GPIO_PIN_0; pin <= GPIO_PIN_5; pin++){
        gpio_set_func(GPIO_PORT_F, pin, IO_FUN_1);
        gpio_set_pull_mode(GPIO_PORT_F, pin, PULL_UP);
        gpio_set_drive_level(GPIO_PORT_F, pin, DRV_LEVEL_2);
    }
}

void sdio_config_clk_width(rt_uint32_t clk,rt_uint32_t width){
    rt_uint32_t rval = 0;
    /* disable card clock */
    //logout("dis card clock\r\n");
    rval = mmc->ckcr_reg;
    rval &= ~(1 << 16);
    mmc->ckcr_reg = rval;
    mmc_update_clk();
    sdelay(1000);
    //logout("en card clock\r\n");
    mmc_set_clk(SDMMC0, clk);
    /* Re-enable card clock */
    rval = mmc->ckcr_reg;
    rval |=  (0x1 << 16); //(3 << 16);
    mmc->ckcr_reg = rval;
    mmc_update_clk();

    mmc->bwdr_reg = width/4; 

    sdelay(1000);
}

void sdio_init(void){
    rt_uint32_t c_size;
    rt_uint32_t resp[4];
    sdio_gpio_init();
    CCU->bus_clk_gating0 |= 0x1 << 8;
    CCU->bus_soft_rst0 |= 0x1 << 8;

    mmc_set_clk(SDMMC0, 24000000);
    sdelay(1000);
    sdio_config_clk_width(400000,1);

    sendcmd(GO_IDLE_STATE,0,VAL_SEND_INIT_SEQ|VAL_CHK_RESP_CRC);//IDLE+INITSEQ
    sdelay(10000);
    read_resp(resp);
    // logout("cmd %d,resp0:%x\r\n",GO_IDLE_STATE,resp[3]);

    sendcmd(SD_SEND_IF_COND,0x1aa,VAL_CHK_RESP_CRC|VAL_CMD_WITH_RESP);//RESP R7
    sdelay(10000);
    read_resp(resp);
    // logout("cmd %d,resp0:%x\r\n",SD_SEND_IF_COND,resp[3]);

    //wait OCR bit 31=1,try 10 times
    int i;
    for(i=0;i<10;i++){
        sendcmd(APP_CMD,0,VAL_CHK_RESP_CRC|VAL_CMD_WITH_RESP);//next CMD is ACMD
        sdelay(10000);
        read_resp(resp);
       // logout("cmd %d,resp0:%x\r\n",APP_CMD,resp[3]);
        sendcmd(SD_APP_OP_COND,0x40018000,VAL_CMD_WITH_RESP);//get OCR
        sdelay(10000);
        read_resp(resp);
       // logout("cmd %d,resp0:%x\r\n",SD_APP_OP_COND,resp[3]);
        if(resp[3] & 0x80000000)
           break;
    }
   // if(i>=10)
   // logout("Not support capacity < 2G!\r\n");
    sendcmd(ALL_SEND_CID,0,VAL_CMD_WITH_RESP|VAL_LONG_RESP|VAL_CHK_RESP_CRC);//get CID
    sdelay(10000);
    read_resp(resp);
    //logout("cmd %d,resp3:%x\r\n",ALL_SEND_CID,resp[0]);
    //logout("cmd %d,resp2:%x\r\n",ALL_SEND_CID,resp[1]);
    //logout("cmd %d,resp1:%x\r\n",ALL_SEND_CID,resp[2]);
    //logout("cmd %d,resp0:%x\r\n",ALL_SEND_CID,resp[3]);

    sendcmd(SET_RELATIVE_ADDR,0,VAL_CMD_WITH_RESP|VAL_CHK_RESP_CRC);//get RCA[31:16]
    sdelay(10000);
    read_resp(resp);
    sd_rca=resp[3] & 0xffff0000;
    //logout("cmd %d,resp0:%x\r\n",SET_RELATIVE_ADDR,sd_rca);

    sendcmd(SEND_CSD,sd_rca,VAL_CMD_WITH_RESP|VAL_LONG_RESP|VAL_CHK_RESP_CRC);//get CSD
    sdelay(10000);
    read_resp(resp);
   // logout("cmd %d,resp3:%x\r\n",SEND_CSD,resp[0]);
   // logout("cmd %d,resp2:%x\r\n",SEND_CSD,resp[1]);
   // logout("cmd %d,resp1:%x\r\n",SEND_CSD,resp[2]);
   // logout("cmd %d,resp0:%x\r\n",SEND_CSD,resp[3]);
    c_size=GET_BITS(resp, 48, 22);
    if(c_size!=0){
        logout("SD capacity %d KB\r\n",(c_size+1)*512);  
    }else{
        logout("Not found SD card!\r\n");
        return;
    } 
    sendcmd(SELECT_CARD,sd_rca,VAL_CMD_WITH_RESP|VAL_CHK_RESP_CRC);//select card into trans state
    sdelay(10000);
    switch_HS4bit_mode();
    // sdio_config_clk_width(1000000,1);
}

void switch_HS4bit_mode(void){
    rt_uint32_t dump;
    rt_uint32_t timeout;
    mmc->bksr_reg = 64;
    mmc->bycr_reg = 64;
    mmc->gctl_reg = mmc->gctl_reg | 0x80000000;
    mmc->cagr_reg = 0x00fffff1;

    mmc->cmdr_reg = 0x80000000|SWITCH|VAL_CMD_WITH_RESP|VAL_CHK_RESP_CRC|VAL_WITH_DATA_TRANS|VAL_WAIT_PRE_OVER;

    timeout=200000;
    for (int i = 0; i < 16; i++) {
        while((mmc->star_reg & (1 << 2)) && --timeout);
        if(timeout<=0){
            logout("[0]switch HS4bit timeout!\r\n");
            break;
        }
        dump = mmc->fifo_reg;
        timeout=200000;
    }

    mmc->bksr_reg = 64;
    mmc->bycr_reg = 64;
    mmc->gctl_reg = mmc->gctl_reg | 0x80000000;
    mmc->cagr_reg = 0x80fffff1;
    mmc->cmdr_reg = 0x80000000|SWITCH|VAL_CMD_WITH_RESP|VAL_CHK_RESP_CRC|VAL_WITH_DATA_TRANS|VAL_WAIT_PRE_OVER;
    timeout=200000;
    for (int i = 0; i < 16; i++) {
        while((mmc->star_reg & (1 << 2)) && --timeout);
        if(timeout<=0){
            logout("[1]switch HS4bit timeout!\r\n");
            break;
        }
        dump = mmc->fifo_reg;
        timeout=200000;
    }
    dump=dump;
    //logout("into HS speed\r\n");  
    sdio_config_clk_width(50000000,1);

    sendcmd(APP_CMD,sd_rca,VAL_CHK_RESP_CRC|VAL_CMD_WITH_RESP);//next CMD is ACMD
    sendcmd(SD_APP_SET_BUS_WIDTH,0x2,VAL_CHK_RESP_CRC|VAL_CMD_WITH_RESP);
    //logout("width = 4\r\n");  
    sdio_config_clk_width(50000000,4);
}

void sdio_write_sector(rt_uint32_t addr_sec,rt_uint8_t *buf){
    rt_uint32_t timeout;
    rt_uint32_t *pdat=(rt_uint32_t *)buf;
    mmc->bksr_reg = 512;
    mmc->bycr_reg = 512;
    mmc->gctl_reg = mmc->gctl_reg | 0x80000000;
    mmc->cagr_reg = addr_sec;

    mmc->cmdr_reg = 0x80000000|WRITE_BLOCK|VAL_CMD_WITH_RESP|VAL_CHK_RESP_CRC|VAL_WITH_DATA_TRANS|VAL_WAIT_PRE_OVER|VAL_TRANS_DIR_WRITE;

    timeout=200000;
    for (int i = 0; i < 128; i++) {
        while((mmc->star_reg & (1 << 3)) && --timeout);
        if (timeout <= 0){
            logout("write data timeout!\r\n");
           break;
        }
        mmc->fifo_reg = pdat[i];
        timeout=200000;
    }
}

void sdio_read_sector(rt_uint32_t addr_sec,rt_uint8_t *buf){
    rt_uint32_t timeout;
    rt_uint32_t *pdat=(rt_uint32_t *)buf;
    mmc->bksr_reg = 512;
    mmc->bycr_reg = 512;
    mmc->gctl_reg = mmc->gctl_reg | 0x80000000;
    mmc->cagr_reg = addr_sec;

    mmc->cmdr_reg = 0x80000000|READ_SINGLE_BLOCK|VAL_CMD_WITH_RESP|VAL_CHK_RESP_CRC|VAL_WITH_DATA_TRANS|VAL_WAIT_PRE_OVER;

    timeout=200000;
     for (int i = 0; i < 128; i++) {
        while((mmc->star_reg & (1 << 2)) && --timeout);
        if (timeout <= 0){
            logout("read data timeout!\r\n");
           break;
        }
        pdat[i] = mmc->fifo_reg;
        timeout=200000;
    }
}

void read_resp(rt_uint32_t *resp){
    resp[3] = mmc->resp0_reg;
    resp[2] = mmc->resp1_reg;
    resp[1] = mmc->resp2_reg;
    resp[0] = mmc->resp3_reg;
}

void sendcmd(rt_int32_t cmd,rt_int32_t arg,rt_int32_t cmdval){
    rt_uint32_t timeout = 100;
    rt_uint32_t val;

    mmc->cagr_reg=arg;
    val=cmdval|cmd|0x80000000;
   // logout("send %x,%x\r\n",0x7fffffff & val, 0x7fffffff & arg);
    mmc->cmdr_reg=val;
    while ((mmc->cmdr_reg & SDXC_LOAD_CMD) && (--timeout)){
        sdelay(1000);
    }
    if (!timeout){
        logout("Run cmd failed\r\n");
    }
    /* clean interrupt */
    mmc->risr_reg = mmc->risr_reg;
}

rt_uint16_t toshort(rt_uint8_t *pd){
    return (pd[0]+pd[1]*0x100);
}

rt_uint32_t toint(rt_uint8_t *pd){
    return (pd[0]+pd[1]*0x100+pd[2]*0x10000+pd[3]*0x1000000);
}

int cpfile_to_ram(char *file,rt_uint32_t addr_ram){
    rt_uint16_t is_fat1_sec;
    rt_uint32_t is_dbr_sec;
    rt_uint32_t temp;

    ALIGN(4) rt_uint8_t  databuf[512];

    mfile.filesize=0;
    mfile.start_cu=0;

    logout("start cp file to ram addr ");
    print_hex(addr_ram);
    sdio_read_sector(0,databuf);//read 0 sector

    is_fat1_sec=toshort(&databuf[0x0e]);
    is_dbr_sec=toint(&databuf[0x1c6]);

    sdio_read_sector(is_fat1_sec,databuf);
    temp=toint(&databuf[0]);
    if(temp==0x0ffffff8){
        sdft32.dbr=0;
        sdft32.fat1=is_fat1_sec;
        logout("fonnd fat1 by dbr[dbr %d][fat1 %d]\r\n",sdft32.dbr,sdft32.fat1);
    }else{
        sdio_read_sector(is_dbr_sec,databuf);
        is_fat1_sec=toshort(&databuf[0x0e]);
        sdft32.fat1=is_fat1_sec+is_dbr_sec;

        sdio_read_sector(sdft32.fat1,databuf);
        temp=toint(&databuf[0]);
        if(temp==0x0ffffff8){
            sdft32.dbr=is_dbr_sec;
            sdft32.fat1=sdft32.fat1;
            logout("fonnd fat1 by mbr[dbr %d][fat1 %d]\r\n",sdft32.dbr,sdft32.fat1);
        }else{
            logout("file system err!\r\n");
            return -1;
        }
    }

    sdio_read_sector(sdft32.dbr,databuf);
    sdft32.fatsize=toint(&databuf[0x24]);
    sdft32.cusize=databuf[0x0d];
    sdft32.root=sdft32.fat1+2*sdft32.fatsize;
    logout("root %d cusize %d fatsize %d\r\n",sdft32.root,sdft32.cusize,sdft32.fatsize);

    rt_uint32_t cunum=2;//root dir cu
    rt_uint32_t rootbase=0;
    rt_uint32_t rootoffset=0;
    rt_uint32_t err;

    do{
        rootbase=get_sector_addr_bycuNo(&sdft32,cunum);
        sdio_read_sector(rootbase+rootoffset,databuf);
        for(int i=0;i<16;i++){
            //scan a sector
            err=0;
            for(int n=0;n<11;n++){
                if(file[n]!=(char)databuf[i*32+n]){
                    err=1;
                    break;
                }                    
            }
            if(err>0){
                continue;
            }else{
                logout("find file ok\r\n");
                logout("cu %d base %d offset %d addr %d\r\n",cunum,rootbase,rootoffset,i);
                mfile.filesize = toint(&databuf[i*32+28]);
                mfile.start_cu = toshort(&databuf[i*32+26]);//L:16bit
                mfile.start_cu += 65536*toshort(&databuf[i*32+20]);//H:16bit
                logout("filesize %d,startform %d cu\r\n",mfile.filesize,mfile.start_cu);
                break;
            }
        }
        if(err>0){
            rootoffset++;//next sector
            if(rootoffset>=sdft32.cusize){
                rootoffset=0;
                cunum=get_nextcuNo_byfatTable(&sdft32,cunum);
                if(cunum==0 || cunum==0x0fffffff){
                        logout("not found file \r\n");
                        break;
                }
            }
        }else{
            break;
        }
    } while (1);
             
    if(mfile.filesize!=0){
        mfile.num_of_secs=mfile.filesize/512;
        if( (mfile.filesize % 512) !=0)
            mfile.num_of_secs++;
        
        mfile.num_of_cus=mfile.num_of_secs/sdft32.cusize;
        if( (mfile.num_of_cus % sdft32.cusize) !=0)
            mfile.num_of_cus++;
        
        logout("file num of cus %d\r\n",mfile.num_of_cus);
        logout("file num of secs %d\r\n",mfile.num_of_secs);

        rt_uint32_t scuNo=mfile.start_cu;
        rt_uint32_t baseaddr;
        rt_uint32_t offsetaddr=0;
        rt_uint32_t *pread,*pwrite;
        rt_uint32_t s=0;

        pread=(rt_uint32_t *)&databuf[0];
        pwrite=(rt_uint32_t *)addr_ram;
        for(int i=0;i<mfile.num_of_secs;i++){
            baseaddr=get_sector_addr_bycuNo(&sdft32,scuNo);
            sdio_read_sector(baseaddr+offsetaddr,databuf);
            //cp databuf to ram
            for(int k=0;k<128;k++)
                pwrite[s++]=pread[k];
            //cp databuf to ram
            offsetaddr++;
            if(offsetaddr>=sdft32.cusize){
                offsetaddr=0;
                scuNo=get_nextcuNo_byfatTable(&sdft32,scuNo);
            }
        }
    }else{
        return -2;
    }
    return 0;
}

rt_uint32_t get_sector_addr_bycuNo(fat32_t pfat,rt_uint32_t cuNo){
    rt_uint32_t secaddr;
    rt_uint32_t cusize;

    if (cuNo<2)
     return 0;

    if(cuNo >= (pfat->fatsize)*128)
        return 0;
    secaddr=pfat->root;
    cusize=pfat->cusize;

    secaddr=secaddr+cusize*(cuNo-2);
     // logout("cu %d at %d secaddr\r\n",cuNo,secaddr);
    return secaddr;
}

rt_uint32_t get_nextcuNo_byfatTable(fat32_t pfat,rt_uint32_t cuNo){
    rt_uint32_t base;
    rt_uint32_t offset;
    ALIGN(4) rt_uint8_t pd[512];
    rt_uint32_t addr;
    rt_uint32_t res;
    
    if (cuNo<2)
      return 0;
    if(cuNo >= (pfat->fatsize)*128)
     return 0;
    base=pfat->fat1;
    offset=cuNo/128;
    addr=(cuNo % 128)*4;
    sdio_read_sector(base+offset,pd);
    res=toint(&pd[addr]);
    //logout("cuNO[cur %d next %d]\r\n",cuNo,res);
    return res;
}